/* eslint-disable eqeqeq */
/* eslint-disable no-unused-vars */
import { logger } from '../common'
import { IDisposable } from '../common/lifecycle'
import { badRequest, ResultType } from '../model'
// #ifdef H5
import * as signalR from '@microsoft/signalr'
import { TxtHubProtocol } from '../protocol'
import { createLogger } from '@microsoft/signalr/dist/esm/Utils'
import * as ddEnv from 'dingtalk-jsapi'
import { singleSignOn } from '../../../utils/ddlogin'
// #endif
/**
 * 存储层Hub
 */
export default class StoreHub implements IDisposable {
  // 超时重试时间
  private _timeout: number
  // 是否已经启动
  private _isStarted: boolean
  // http 请求客户端
  private _http: signalR.HttpClient
  // signalr 连接
  private _connection: signalR.HubConnection
  // 连接成功回调
  private _connectedCallbacks: (() => void)[]
  // 连接断开回调
  private _disconnectedCallbacks: ((err: Error | undefined) => void)[]
  /**
   * 构造方法
   * @param url signalr服务地址
   * @param timeout 超时检测默认8000ms
   * @param interval 心跳间隔默认3000ms
   */
  constructor(url: string, protocol = 'json', timeout = 6000, interval = 3000) {
    this._isStarted = false
    this._timeout = timeout
    this._connectedCallbacks = []
    this._disconnectedCallbacks = []
    // #ifdef H5
    let hubProtocol: signalR.IHubProtocol = new signalR.JsonHubProtocol()
    if (protocol == 'txt') {
      hubProtocol = new TxtHubProtocol()
    }
    const _logger = createLogger(signalR.LogLevel.Error)
    this._http = new signalR.DefaultHttpClient(_logger)
    this._connection = new signalR.HubConnectionBuilder()
      .withUrl(url)
      .configureLogging(_logger)
      .withHubProtocol(hubProtocol)
      .build()
    this._connection.serverTimeoutInMilliseconds = timeout
    this._connection.keepAliveIntervalInMilliseconds = interval
    this._connection.onclose(err => {
      if (this._isStarted) {
        this._disconnectedCallbacks.forEach(c => {
          c.apply(this, [err])
        })
        logger.warn(
          `连接断开,${this._timeout}ms后重试。` + (err ? err!.message : '')
        )
        setTimeout(() => {
          this._starting()
        }, this._timeout)
      }
    })
    // #endif
  }
  /**
   * 是否处于连接着的状态
   * @return {boolean} 状态
   */
  public get isConnected(): boolean {
    let res = null
    // #ifdef H5
    res =
      this._isStarted &&
      this._connection.state === signalR.HubConnectionState.Connected
    // #endif

    // #ifndef H5
    res = false
    // #endif
    return res
  }
  // 获取accessToken
  public get accessToken(): string {
    let token = uni.getStorageSync('accessToken') || ''
    return token
  }
  // 设置accessToken
  private set accessToken(val: string) {
    // #ifdef H5
    sessionStorage.setItem('accessToken', val)
    // #endif

    // #ifndef H5
    uni.setStorageSync('accessToken', val)
    // #endif
  }
  /**
   * 销毁连接
   * @returns {Promise<void>} 异步Promise
   */
  public dispose(): Promise<void> {
    // #ifdef H5
    this._isStarted = false
    this._connectedCallbacks = []
    this._disconnectedCallbacks = []
    return this._connection.stop()
    // #endif
  }
  /**
   * 启动链接
   * @returns {void} 无返回值
   */
  public start(): void {
    if (!this._isStarted) {
      this._isStarted = true
      if (!this.isConnected) {
        this._starting()
      }
    }
  }
  /**
   * 重新建立连接
   * @returns {void} 无返回值
   */
  public async restart(): Promise<void> {
    // #ifdef H5
    if (this.isConnected) {
      this._isStarted = false
      await this._connection.stop()
      setTimeout(() => {
        this.start()
      }, 1000)
    }
    // #endif
  }
  /**
   * 开始连接
   * @returns {void} 无返回值
   */
  private async _starting(): Promise<void> {
    // #ifdef H5
    this._connection
      .start()
      .then(() => {
        this._connectedCallbacks.forEach(c => {
          c.apply(this, [])
        })
      })
      .catch(err => {
        this._disconnectedCallbacks.forEach(c => {
          c.apply(this, [err])
        })
        setTimeout(() => {
          this._starting()
        }, this._timeout)
      })
    // #endif
  }
  /**
   * 连接成功事件
   * @param {Function} callback 回调
   * @returns {void} 无返回值
   */
  public onConnected(callback: () => void): void {
    if (callback) {
      this._connectedCallbacks.push(callback)
    }
  }
  /**
   * 断开连接事件
   * @param {Function} callback 回调
   * @returns {void} 无返回值
   */
  public onDisconnected(callback: (err: Error | undefined) => void): void {
    if (callback) {
      this._disconnectedCallbacks.push(callback)
    }
  }
  /**
   * 监听服务端方法
   * @param {string} methodName 方法名
   * @param {Function} newMethod 回调
   * @returns {void} 无返回值
   */
  public on(methodName: string, newMethod: (...args: any[]) => any): void {
    // #ifdef H5
    this._connection.on(methodName, newMethod)
    // #endif
  }
  /**
   * 请求服务端方法
   * @param {string} methodName 方法名
   * @param {any[]} args 参数
   * @returns {Promise<ResultType>} 异步结果
   */
  public invoke(methodName: string, ...args: any[]): Promise<ResultType<any>> {
    return new Promise((resolve, reject) => {
      // #ifndef H5
      //非登录请求，且无token 拦截请求
      if (!['Auth', 'Login'].includes(methodName) && !this.accessToken) {
        return reject(false)
      }
      // #endif
      const success = (res: ResultType<any>) => {
        if (!res.success) {
          if (res.code === 401) {
            // #ifdef H5 || MP-ALIPAY
            if (ddEnv.env.platform === 'notInDingTalk') {
              this.accessToken = ''
              uni.removeStorageSync('accessToken')
              uni.reLaunch({ url: '/pages/index/index' })
            } else {
              singleSignOn()
            }
            // #endif

            // #ifndef H5 
            logger.unauth()
            this.accessToken = ''
            uni.removeStorageSync('accessToken')
            uni.reLaunch({ url: '/pages/index/index' })
            // #endif
          } else if (res.msg != '' && !res.msg.includes('不在线')) {
            logger.warn('操作失败,' + res.msg)
          }
        }
        resolve(res)
      }
      const error = (reason: any) => {
        // #ifndef H5
        if (reason.statusCode === 401) {
          this.accessToken = ''
          uni.removeStorageSync('accessToken')
          uni.reLaunch({ url: '/pages/index/index' }).then(() => {
            uni.$tm.u.toast(reason.data, true, 'error')
          })
        }
        // #endif
        let msg = '请求异常'
        if (reason && reason.Error) {
          msg += ',' + reason.Error()
          logger.warn(msg)
        }
        resolve(badRequest(msg))
      }
      // #ifdef H5
      if (this.isConnected) {
        this._connection
          .invoke(methodName, ...args)
          .then(success)
          .catch(error)
      } else {
        // #endif
        this.restRequest(
          'post',
          '/orginone/kernel/rest/' + methodName.toLowerCase(),
          args.length > 0 ? args[0] : {}
        )
          .then(success)
          .catch(error)
        // #ifdef H5
      }
      // #endif
    })
  }
  /**
   * 向连接发送数据
   * @param {string} methodName 方法名
   * @param {any[]} args 参数
   * @returns {Promise<T|undefined>} 异步结果
   */
  public async send<T>(
    methodName: string,
    ...args: any[]
  ): Promise<T | undefined> {
    // #ifdef H5
    if (this.isConnected) {
      return await this._connection.invoke(methodName, ...args)
    }
    // #endif
  }
  /**
   * Http请求服务端方法
   * @param {string} methodName 方法名
   * @param {any[]} args 参数
   * @returns {Promise<ResultType>} 异步结果
   */
  public async restRequest(
    method: string,
    url: string,
    args: any
  ): Promise<ResultType<any>> {
    let res: any = null
    // #ifdef H5
    res = await this._http.send({
      url: url,
      timeout: 30000,
      method: method,
      content: JSON.stringify(args),
      headers: {
        Authorization: this.accessToken
      }
    })
    // #endif

    // #ifndef H5
    res = await uni.$tm.fetch.request({
      url: `https://assetcloud.zj.gov.cn${url}`,
      header: { Authorization: this.accessToken, 'Content-Type': 'application/json' },
      method: method.toUpperCase() as 'POST',
      timeout: 10000,
      data: JSON.stringify(args),
      dataType: 'json'
    })
    // #endif
    if (res.statusCode === 200 && typeof res.content === 'string') {
      return JSON.parse(res.content.replace(/"_id":/gm, '"id":'))
    } else if (res.statusCode !== 200) {
      return badRequest(res.statusText, res.statusCode)
    } else if (res.statusCode === 200 && res.data.code === 200) {
      return res.data
    } else if (res.data.code !== 200) {
      return badRequest(res.data.msg, res.data.code)
    }
  }
}
